很多团队的现实情况是:
- 不想强依赖 VS Code 的 Prettier 扩展(每个人装的版本不一、设置不一、偶尔还会和别的格式化器打架)。
- 想把“格式化”收敛到项目层:命令行统一、CI 统一、提交前统一。
- 同时又想享受保存即格式化的手感。
这篇文章分享一个我在项目里常用的通用方案:
- JS/TS/JSX/TSX:交给 oxfmt(快、稳定、偏工程化)。
- Markdown/JSON/YAML/CSS/HTML 等:交给 prettier CLI(生态成熟、覆盖面广)。
- VS Code 端:不装 Prettier 扩展,仍然做到“能一键格式化、能提交前自动格式化、需要的话也能自动保存触发格式化”。
重要前提:oxfmt 目前没有 VS Code 官方/主流 formatter 扩展(至少在多数团队的默认配置里是这样),所以本文的所有 oxfmt 都按“命令行工具(CLI)”来使用:
- 你可以用 RunOnSave 在保存时触发它
- 也可以用 VS Code Task 一键触发
- 最稳的是提交前钩子(lint-staged)兜底
先把原则定清楚:格式化应该归谁管?
我推荐的优先级是:
- 项目工具链(pnpm scripts + CLI)是最终裁判:任何人、任何 IDE,跑同一条命令,输出一致。
- VS Code 只是触发器:
- 能自动触发 CLI 的(例如用 RunOnSave)就做到“保存即格式化”。
- 其他情况(例如不想在保存时跑命令),就交给“任务 / 提交钩子 / CI”。
- 永远避免“双格式化”:同一类文件只交给一个 formatter。
这样做最大的好处:你不靠某个编辑器扩展也能保证一致性。
分工:oxfmt 管 JS/TS,prettier 管其余
为什么 oxfmt 适合 JS/TS?
- 快:保存时格式化的体感更好。
- 和现代前端工程化思路更贴合:把“统一风格”变成工具链的一部分。
为什么 prettier 仍然要保留?
因为它的覆盖面太强了:Markdown、YAML、JSON、HTML、CSS……很多项目里“真正乱”往往就乱在这些文件上。
方案总览(你照这个搭就能跑)
你需要准备 5 类东西:
- prettier 配置(项目根:
prettier.config.*/.prettierrc*+.prettierignore) - oxfmt 配置(项目根:
.oxfmt*或 oxfmt/oxc 相关的配置文件,按你的工具版本来) - pnpm scripts:一键格式化(全量/增量)
- VS Code
.vscode/settings.json:让 JS/TS 保存时走 oxfmt - VS Code
.vscode/tasks.json+ 提交钩子:不靠 Prettier 扩展也能格式化 Markdown/JSON/YAML…
下面我按“你能直接复制走”的方式给出参考配置。
第 1 步:prettier 只管它该管的文件(避免和 oxfmt 打架)
1).prettierignore:把 JS/TS 排除掉
核心思路:让 prettier 不碰 JS/TS(否则你会遇到“保存格式化一次、提交又格式化一次、diff 永远有变化”的地狱)。
例如:
# 让 oxfmt 负责 JS/TS/JSX/TSX
**/*.{js,jsx,ts,tsx}
# 构建产物
dist
.vitepress/dist
node_modules
# 其他你不想格式化的目录
**/CHANGELOG.md
2)prettier.config.*:把“可控的格式”写死
你可以用 prettier.config.mjs 或 .prettierrc,关键是可被 CLI 读到。
配置项别搞花活,推荐只锁几个关键的:
// prettier.config.mjs(示例)
export default {
printWidth: 100,
tabWidth: 4,
singleQuote: true,
semi: false,
trailingComma: "all",
proseWrap: "preserve"
};
注意:你项目里 Markdown 的排版习惯(比如列表缩进、中文标点与英文间距)会直接影响读感,建议把
printWidth和tabWidth固化,避免不同人不同风格。
第 2 步:pnpm scripts 统一入口(“不靠扩展”的关键)
你最终要的体验是:
- 格式化全项目:
pnpm format - 只格式化某个文件:
pnpm format:file -- <file> - 提交前自动格式化变更文件:git hooks + lint-staged
一个常用脚本结构是:
{
"scripts": {
"format": "pnpm -s format:oxfmt && pnpm -s format:prettier",
"format:oxfmt": "pnpm -s exec oxfmt --write .",
"format:prettier": "pnpm -s exec prettier --write . --cache --ignore-unknown",
"format:file": "node scripts/format-file.mjs"
}
}
补充一句:这里用 && 是为了在 Windows/macOS/Linux 都能稳定串行执行;不要用 ;,否则在 Windows 的默认 shell 下可能会翻车。
说明:
format只是聚合命令,保证任何人执行顺序一致。format:oxfmt/format:prettier分开写,方便在任务/钩子里只跑需要的那一段。
如果你不想写 scripts/format-file.mjs,也可以只做“全量 + 提交前增量”,足够覆盖 90% 场景。
第 3 步:VS Code 不装 Prettier 扩展,也能“保存即格式化”(用 CLI 触发)
这里先把“真相”说清楚:
- VS Code 的
editor.formatOnSave依赖“某个扩展提供 formatter”。 - 既然 oxfmt 没有 VS Code formatter 扩展,那就没法靠
formatOnSave直接调用 oxfmt。
所以我们用两条路来实现“保存即格式化”的体验(本质都是保存时触发 CLI):
- 让 RunOnSave 在保存时调用
oxfmt(适用于 JS/TS)。 - 让 RunOnSave 在保存时调用
prettier(适用于 Markdown/JSON/YAML/HTML/CSS)。
这也是本文的核心:用 CLI 做统一入口,用一个“保存触发器”把体验补齐。
第 4 步:不装 Prettier 扩展时,Markdown/JSON/YAML 怎么格式化?
这里要分清楚一件事:
- VS Code 的“保存即格式化”依赖 formatter provider。
- 你不装 Prettier 扩展时,很多文件类型(尤其 Markdown)就没有你想要的 provider。
所以我们用“项目工具链”的方式补上:
方案 A(我最推荐):提交前自动格式化(lint-staged)
这是“最不折腾、最一致”的方案:你可以不在每次保存时格式化 Markdown,但你提交时一定会被格式化。
典型配置(你可以用 simple-git-hooks 或 husky,二选一):
{
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": "pnpm -s exec oxfmt --write",
"*.{css,scss,less,html,json,md,yaml,yml}": "pnpm -s exec prettier --write --cache --ignore-unknown"
}
}
关键点:
- 文件后缀分流:JS/TS 走 oxfmt,其余走 prettier。
- 这就实现了:不装 Prettier 扩展也能统一风格。
方案 B:VS Code 一键任务(格式化当前文件 / 全项目)
你可以在 .vscode/tasks.json 里加两个任务:
Format (workspace):跑pnpm formatFormat (current file):跑prettier --write ${file}或根据后缀选择 oxfmt/prettier
这样你就能在 VS Code 里:
- 需要时按一下快捷键(或命令面板选任务),把 Markdown/JSON/YAML 也一把梭。
这个方案的好处是:零依赖 Prettier 扩展。坏处是:它不是自动的,需要你主动触发一次。
方案 C(想要“保存即格式化”但不装 Prettier 扩展):用 RunOnSave 在保存时触发 CLI
如果你真的特别在意 Markdown 的“保存即格式化”,但仍不想装 Prettier 扩展,那只能走“保存时跑 CLI”的路线。
实现方式我推荐直接用这个扩展:
- Run on Save(扩展 ID:
emeraldwalk.RunOnSave) - Marketplace:
https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave
我自己用 fetch_webpage 抓过它的说明页,确认它支持:
- 用
match/notMatch做正则匹配(注意:匹配的是绝对路径) - 保存时执行命令(支持串行/并行
isAsync) - 支持
${file}/${workspaceFolder}/${fileBasename}等占位符
下面给一个“够用且不容易炸”的配置范式(放到工作区的 .vscode/settings.json 或用户设置里都行):
{
"emeraldwalk.runonsave": {
"showElapsed": true,
"commands": [
{
// 让 oxfmt 负责的文件:js/ts/jsx/tsx
"match": "\\.(js|jsx|ts|tsx)$",
"notMatch": "(\\\\node_modules\\\\|\\\\dist\\\\|\\\\\\.vitepress\\\\dist\\\\|\\\\\\.vscode\\\\)",
"cmd": "pnpm -s exec oxfmt --write \"${file}\""
},
{
// 让 prettier 负责的文件:md/json/yaml/html/css...
"match": "\\.(md|json|ya?ml|yml|html|css|scss|less)$",
// 不要动依赖/产物/编辑器配置目录
// 注意:Windows 下路径分隔符是 \\,而 JSON 里反斜杠要双重转义
"notMatch": "(\\\\node_modules\\\\|\\\\dist\\\\|\\\\\\.vitepress\\\\dist\\\\|\\\\\\.vscode\\\\)",
"cmd": "pnpm -s exec prettier --write --cache --ignore-unknown \"${file}\""
}
]
}
}
这样你就能做到:
- JS/TS:保存时由 RunOnSave 触发
oxfmt(CLI) - Markdown/JSON/YAML/HTML/CSS:保存时由 RunOnSave 触发 prettier CLI
官方还特别提醒了一点(很关键):RunOnSave 只会在“保存已有文件”时触发,新建文件第一次保存、以及“另存为”不会触发——这不是 bug,是它的设计。
我个人仍然更推荐 A + B;如果你真的强依赖“文档保存即格式化”,再加上 C:
- 写代码(JS/TS)靠 oxfmt 保存即格式化
- 文档类靠任务一键
- 提交前靠 lint-staged 保底
体验足够好,而且不会引入新的不稳定因素。
常见坑(提前踩掉)
1)同一文件被两套格式化器处理
症状:你总觉得“格式化完又乱了”“diff 永远有”。
解决:
- 让 prettier 忽略
*.{js,ts,jsx,tsx}(或让 lint-staged 分流)。 - VS Code 的默认 formatter 只对 JS/TS 指向 oxfmt。
2)Windows 路径与引号
任务里用 ${file} 时,如果路径包含空格,命令行引号一定要处理好。
建议做法:
- 优先跑
pnpm format(全量)或lint-staged(增量),避免自己在 tasks 里拼复杂引号。
3)团队里有人用 WebStorm / Cursor / 其他 IDE
这套方案天然兼容:
- 他们只要能跑
pnpm format/ 提交代码,就会自动统一风格。 - VS Code 里的“格式化体验”只是锦上添花,不是硬依赖。
最后总结(一句话)
把格式化收敛到项目工具链:JS/TS 用 oxfmt(编辑器保存即格式化),其余用 prettier CLI(任务 + 提交前钩子兜底),需要“保存即格式化”时就用 emeraldwalk.RunOnSave 触发 prettier CLI ——就能做到“不装 Prettier 扩展也一致”。
